# coding=utf-8 ################################################################
import ctypes
import platform

# Constant
NID_secp192k1 = 711

class OpenSSLError(Exception): pass
class OpenSSLDataError(Exception): pass

class OpenSSL:
    def __init__(self):
        if platform.architecture() == ('64bit', 'WindowsPE'):
            self.lib = ctypes.CDLL("libeay32_64.dll")
        elif platform.architecture() == ('32bit', 'WindowsPE'):
            self.lib = ctypes.CDLL("libeay32_32.dll")
        else:
            raise Exception("Platform not supported yet")
        
    def ec_key_new_by_curve_name(self, curve):
        func = self.lib.EC_KEY_new_by_curve_name
        func.argtypes = [ctypes.c_int]
        func.restype = ctypes.c_void_p
        r = ctypes.c_void_p(func(curve))
        if not r:
            raise OpenSSLError("Couldn't create key")
        return r

    def ec_key_free(self, key):
        func = self.lib.EC_KEY_free
        func.argtypes = [ctypes.c_void_p]
        func.restype = None
        func(key)
    
    def ec_key_generate_key(self, key):
        func = self.lib.EC_KEY_generate_key
        func.argtypes = [ctypes.c_void_p]
        func.restype = ctypes.c_int
        r = func(key)
        if r != 1:
            raise OpenSSLError("Couldn't generate key")
    
    def i2d_ecprivatekey(self, key, buf):
        func = self.lib.i2d_ECPrivateKey
        func.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
        func.restype = ctypes.c_int
        r = func(key, buf)
        if r == 0:
            raise OpenSSLError("Couldn't encode private key")
        return r
    
    def d2i_ecprivatekey(self, pkey, pbuf, buf_len):
        func = self.lib.d2i_ECPrivateKey
        func.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
                         ctypes.c_int]
        func.restype = ctypes.c_void_p
        r = ctypes.c_void_p(func(pkey, pbuf, buf_len))
        if not r:
            raise OpenSSLDataError("Couldn't decode private key")
        return r
        
    def i2o_ecpublickey(self, key, buf):
        func = self.lib.i2o_ECPublicKey
        func.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
        func.restype = ctypes.c_int
        r = func(key, buf)
        if r == 0:
            raise OpenSSLError("Couldn't encode public key")
        return r
    
    def o2i_ecpublickey(self, pkey, pbuf, buf_len):
        func = self.lib.o2i_ECPublicKey
        func.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
                         ctypes.c_int]
        func.restype = ctypes.c_void_p
        r = ctypes.c_void_p(func(pkey, pbuf, buf_len))
        if not r:
            raise OpenSSLDataError("Couldn't decode public key")
        return r
    
    def ecdsa_size(self, key):
        func = self.lib.ECDSA_size
        func.argtypes = [ctypes.c_void_p]
        func.restype = ctypes.c_int
        r = func(key)
        return r

    def ecdsa_sign(self, type, dgst, dgstlen, sig, siglen, eckey):
        func = self.lib.ECDSA_sign
        func.argtypes = [ctypes.c_int,
                         ctypes.c_void_p,
                         ctypes.c_int,
                         ctypes.c_void_p,
                         ctypes.POINTER(ctypes.c_int),
                         ctypes.c_void_p]
        func.restype = ctypes.c_int
        r = func(type, dgst, dgstlen, sig, siglen, eckey)
        if r != 1:
            raise OpenSSLError("Couldn't sign")
        return r
        
    def ecdsa_verify(self, type, dgst, dgstlen, sig, siglen, eckey):
        func = self.lib.ECDSA_verify
        func.argtypes = [ctypes.c_int,
                         ctypes.c_void_p,
                         ctypes.c_int,
                         ctypes.c_void_p,
                         ctypes.c_int,
                         ctypes.c_void_p]
        func.restype = ctypes.c_int
        r = func(type, dgst, dgstlen, sig, siglen, eckey)
        if r == -1:
            raise OpenSSLError("Couldn't verify signature")
        return r
    
    def err_get_error(self):
        func = self.lib.ERR_get_error
        func.argtypes = []
        func.restype = ctypes.c_ulong
        r = func()
        return r
    
    def generate_key_pair(self):        
        # Alloc and generate key
        key = self.ec_key_new_by_curve_name(NID_secp192k1)
        self.ec_key_generate_key(key)
        
        # Encode private key
        private_key_len = self.i2d_ecprivatekey(key, 0)
        private_key_data = ctypes.create_string_buffer(private_key_len)
        private_key_data_pointer = ctypes.pointer(private_key_data)
        self.i2d_ecprivatekey(key, ctypes.byref(private_key_data_pointer))
        
        # Encode public key
        public_key_len = self.i2o_ecpublickey(key, 0)
        public_key_data = ctypes.create_string_buffer(public_key_len)
        public_key_data_pointer = ctypes.pointer(public_key_data)
        self.i2o_ecpublickey(key, ctypes.byref(public_key_data_pointer))
        
        # Free key and return
        self.ec_key_free(key)
        return {"public": public_key_data[:], "private": private_key_data[:]}
    
    def verify(self, key_buf, signature, hash):
        # Decode key_buf into key
        key_data = ctypes.create_string_buffer(key_buf)
        key_data_pointer = ctypes.pointer(key_data)
        key = self.ec_key_new_by_curve_name(NID_secp192k1)
        self.o2i_ecpublickey(ctypes.byref(key), 
                             ctypes.byref(key_data_pointer),
                             len(key_buf))
        
        # Verify the signature
        signature_data = ctypes.create_string_buffer(signature)
        hash_data = ctypes.create_string_buffer(hash)
        r = self.ecdsa_verify(0, hash_data, len(hash), 
                              signature_data, len(signature), key)
        
        # Free key
        self.ec_key_free(key)
        
        return r == 1
        
    def sign(self, key_buf, hash):
        # Decode key_buf into key
        key_data = ctypes.create_string_buffer(key_buf)
        key_data_pointer = ctypes.pointer(key_data)
        key = self.ec_key_new_by_curve_name(NID_secp192k1)
        self.d2i_ecprivatekey(ctypes.byref(key), 
                             ctypes.byref(key_data_pointer),
                             len(key_buf))
        
        # Sign the hash
        sig_maxlen = self.ecdsa_size(key)
        sig = ctypes.create_string_buffer(sig_maxlen)
        sig_len = ctypes.c_int(sig_maxlen)
        hash_data = ctypes.create_string_buffer(hash)
        self.ecdsa_sign(0, 
                   hash_data, 
                   len(hash),
                   sig,
                   ctypes.byref(sig_len),
                   key)
        
        # Free key and return signature
        self.ec_key_free(key)
        return sig[:sig_len.value]

def generate_key_pair(name):
    lib = OpenSSL()
    r = lib.generate_key_pair()
    r["name"] = name
    return r

def sign(key, hash):
    lib = OpenSSL()
    return lib.sign(key, hash)

def verify(key, signature, hash):
    lib = OpenSSL()
    return lib.verify(key, signature, hash)
